package com.lvyuely.web.module.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.GZIPInputStream;

import org.apache.commons.compress.archivers.ArchiveEntry;
import org.apache.commons.compress.archivers.ArchiveInputStream;
import org.apache.commons.compress.archivers.ArchiveOutputStream;
import org.apache.commons.compress.archivers.ArchiveStreamFactory;
import org.apache.commons.compress.archivers.jar.JarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;

public class CompressAndExtractFileUtils {
	private final static CompressAndExtractFileUtils instance = new CompressAndExtractFileUtils();
	private final static String DEFAULT_ENCODING = "UTF-8";

	private CompressAndExtractFileUtils() {
	}

	public void compressFile(String srcPath, String destPath) {
		compressFile(srcPath, destPath, DEFAULT_ENCODING);
	}

	public void compressFile(String srcPath, String destPath, String encoding) {
		FileOutputStream fos = null;
		ArchiveOutputStream os = null;
		try {
			if (StringUtils.isEmpty(encoding)) {
				encoding = DEFAULT_ENCODING;
			}
			File destFile = mkFile(destPath);// mkFile
			fos = new FileOutputStream(destFile);
			String extension = getExtension(destPath);
			os = getArchiveOutputStream(extension, fos, encoding);
			compressFile(os, extension, srcPath);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(os);
			IOUtils.closeQuietly(fos);
		}

	}

	private void compressFile(ArchiveOutputStream os, String extension, String srcPath) {
		String basePath = getBasePath(srcPath);
		if ("zip".equals(extension)) {
			compressZipFile(os, srcPath, basePath);
		} else if ("jar".equals(extension)) {
			compressJarFile(os, srcPath, basePath);
		} else if ("tar".equals(extension) || "gz".equals(extension)) {
			compressTarFile(os, srcPath, basePath);
		}

	}

	private void compressJarFile(ArchiveOutputStream os, String srcPath, String basePath) {
		File f = new File(srcPath);
		if (f.isFile()) {
			FileInputStream fis = null;
			try {
				JarArchiveEntry entry = new JarArchiveEntry(srcPath.substring(basePath.length()));
				os.putArchiveEntry(entry);
				fis = new FileInputStream(f);
				IOUtils.copy(fis, os);
				os.closeArchiveEntry();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				IOUtils.closeQuietly(fis);
			}
		} else {
			File[] subFile = f.listFiles();
			for (File sub : subFile) {
				compressJarFile(os, sub.getAbsolutePath(), basePath);
			}
		}
	}

	private void compressTarFile(ArchiveOutputStream os, String srcPath, String basePath) {
		File f = new File(srcPath);
		if (f.isFile()) {
			FileInputStream fis = null;
			try {
				TarArchiveEntry entry = new TarArchiveEntry(f, srcPath.substring(basePath.length()));
				os.putArchiveEntry(entry);
				fis = new FileInputStream(f);
				IOUtils.copy(fis, os);
				os.closeArchiveEntry();
				os.flush();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				IOUtils.closeQuietly(fis);
			}
		} else {
			File[] subFile = f.listFiles();
			for (File sub : subFile) {
				compressTarFile(os, sub.getAbsolutePath(), basePath);
			}
		}
	}

	private void compressZipFile(ArchiveOutputStream os, String srcPath, String basePath) {
		File f = new File(srcPath);
		if (f.isFile()) {
			FileInputStream fis = null;
			try {
				ZipArchiveEntry entry = new ZipArchiveEntry(srcPath.substring(basePath.length()));
				os.putArchiveEntry(entry);
				fis = new FileInputStream(f);
				IOUtils.copy(fis, os);
				os.closeArchiveEntry();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				IOUtils.closeQuietly(fis);
			}
		} else {
			File[] subFile = f.listFiles();
			for (File sub : subFile) {
				compressZipFile(os, sub.getAbsolutePath(), basePath);
			}
		}
	}

	private ArchiveOutputStream getArchiveOutputStream(String extension, FileOutputStream fos, String encoding) throws Exception {
		// create factory
		ArchiveStreamFactory factory = new ArchiveStreamFactory();
		// set entryEncoding
		factory.setEntryEncoding(encoding);
		// return ArchiveOutputStream with extension
		if ("gz".equals(extension) || "tar".equals(extension)) {
			return factory.createArchiveOutputStream(ArchiveStreamFactory.TAR, fos);
		} else if ("zip".equals(extension)) {
			return factory.createArchiveOutputStream(ArchiveStreamFactory.ZIP, fos);
		} else if ("jar".equals(extension)) {
			return factory.createArchiveOutputStream(ArchiveStreamFactory.JAR, fos);
		}
		return null;
	}

	public void extractFile(String srcFile, String destFile) {
		extractFile(srcFile, destFile, DEFAULT_ENCODING);
	}

	public void extractFile(String srcFile, String destFile, String encoding) {
		File file = new File(destFile);
		if (!file.exists()) {
			file.mkdir();
		}
		FileInputStream fis = null;
		ArchiveInputStream in = null;
		try {
			if (StringUtils.isEmpty(encoding)) {
				encoding = DEFAULT_ENCODING;
			}
			fis = new FileInputStream(srcFile);
			String extension = getExtension(srcFile);
			in = getArchiveInputStream(fis, extension, encoding);
			extractFile(in, extension, destFile);
		} catch (Exception e) {
			e.printStackTrace();
		} catch (Throwable t) {
			t.printStackTrace();
		} finally {
			IOUtils.closeQuietly(in);
			IOUtils.closeQuietly(fis);
		}
	}

	private void extractFile(ArchiveInputStream in, String extension, String outputDirectory) {
		if ("gz".equals(extension) || "tar".equals(extension)) {
			extractFile(in, outputDirectory, TarArchiveEntry.class);
		} else if ("zip".equals(extension)) {
			extractFile(in, outputDirectory, ZipArchiveEntry.class);
		} else if ("jar".equals(extension)) {
			extractFile(in, outputDirectory, JarArchiveEntry.class);
		}
	}

	@SuppressWarnings("unchecked")
	public <T extends ArchiveEntry> void extractFile(ArchiveInputStream in, String outputDirectory, Class<T> T) {
		BufferedInputStream bis = null;
		try {
			bis = new BufferedInputStream(in);
			T entry = null;
			while ((entry = (T) in.getNextEntry()) != null) {
				String name = entry.getName();
				System.out.println("name:" + name);
				String[] names = name.split("/");
				String fileName = outputDirectory;
				for (int i = 0; i < names.length; i++) {
					String str = names[i];
					fileName = fileName + File.separator + str;
				}
				if (name.endsWith("/")) {
					mkFolder(fileName);
				} else {
					BufferedOutputStream bos = null;
					try {
						File file = mkFile(fileName);
						bos = new BufferedOutputStream(new FileOutputStream(file));
						IOUtils.copy(in, bos);
					} catch (Exception e) {
						e.printStackTrace();
					} finally {
						IOUtils.closeQuietly(bos);
					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		} catch (Throwable t) {
			t.printStackTrace();
		} finally {
			IOUtils.closeQuietly(bis);
		}
	}

	public void extractZipFile() {

	}

	private void mkFolder(String fileName) {
		System.out.println("make folder[" + fileName + "]");
		File f = new File(fileName);
		if (!f.exists()) {
			f.mkdirs();
		}
	}

	private File mkFile(String fileName) {
		System.out.println("make file[" + fileName + "]");
		File f = new File(fileName);
		try {
			File parent = f.getParentFile();
			if (!parent.exists()) {
				parent.mkdirs();
			}
			f.createNewFile();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return f;
	}

	public static CompressAndExtractFileUtils getInstance() {
		return instance;
	}

	public ArchiveInputStream getArchiveInputStream(FileInputStream fis, String extension, String encoding) throws Exception, Throwable {
		// create factory
		ArchiveStreamFactory factory = new ArchiveStreamFactory();
		// set entryEncoding
		factory.setEntryEncoding(encoding);
		if ("gz".equals(extension) || "tar".equals(extension)) {
			return factory.createArchiveInputStream(ArchiveStreamFactory.TAR, new GZIPInputStream(new BufferedInputStream(fis)));
		} else if ("zip".equals(extension)) {
			return factory.createArchiveInputStream(ArchiveStreamFactory.ZIP, fis);
		} else if ("jar".equals(extension)) {
			return factory.createArchiveInputStream(ArchiveStreamFactory.JAR, fis);
		}
		return null;
	}

	public String getExtension(String path) {
		int index = path.lastIndexOf(".");
		if (index != -1) {
			return path.substring(index + 1, path.length()).toLowerCase();
		}
		return "";
	}

	private String getBasePath(String srcPath) {
		String basePath = srcPath;
		File srcFile = mkFile(srcPath);// mkFile
		if (srcFile.isFile()) {
			basePath = srcFile.getParent();
		} else {
			if (!basePath.endsWith("/")) {
				basePath += File.separator;
			}
		}
		return basePath;
	}

}
